//! Low-level filesystem operation request.
//!
//! A request represents information about a filesystem operation the kernel driver wants us to
//! perform.

use super::fuse_abi::{InvalidOpcodeError, fuse_in_header, fuse_opcode};

use super::{Errno, Response, fuse_abi as abi};
#[cfg(feature = "serializable")]
use serde::{Deserialize, Serialize};
use std::{convert::TryFrom, fmt::Display, path::Path};
use std::{error, fmt, mem};

use super::argument::ArgumentIterator;

/// Error that may occur while reading and parsing a request from the kernel driver.
#[derive(Debug)]
pub enum RequestError {
    /// Not enough data for parsing header (short read).
    ShortReadHeader(usize),
    /// Kernel requested an unknown operation.
    UnknownOperation(u32),
    /// Not enough data for arguments (short read).
    ShortRead(usize, usize),
    /// Insufficient argument data.
    InsufficientData,
}

/// Unique ID for a request from the kernel
///
/// The FUSE kernel driver assigns a unique id to every concurrent request. This allows to
/// distinguish between multiple concurrent requests. The unique id of a request may be
/// reused in later requests after it has completed.
///
/// This can be retrieve for any request using [`Request::unique`].  The kernel
/// will send an [`Interrupt`] request to cancel requests in progress.  It's
/// important to handle this for any requests that may block indefinitely, like
/// [`SetLkW`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))]
pub struct RequestId(pub u64);
impl From<RequestId> for u64 {
    fn from(fh: RequestId) -> Self {
        fh.0
    }
}

/// A newtype for inode numbers
///
/// These are generated by the filesystem implementation and returned to the
/// kernel in response to a call to [`Lookup`], [`Create`], [`MkNod`], [`MkDir`] or
/// [`SymLink`].  The kernel will then pass these numbers back to the filesystem
/// implementation when it needs to refer to a given file.  Every request has
/// an associated [`INodeNo`], accessible as [`Request::nodeid`].
///
/// Reference Counting
/// ------------------
///
/// Every time the kernel receives a given inode number in a response to a
/// [`Lookup`], [`Create`], [`MkNod`], [`MkDir`] or [`SymLink`] request it increments an
/// internal counter for that inode.  The filesystem implementation should do
/// the same.  When the kernel is no longer interested in this inode it will
/// send a [`Forget`] message with that counter.  The filesystem implementation
/// should decrement its own counter and if it reaches 0 then the inode number
/// may be recycled and your filesystem implementation may clean up its
/// internal data-structures relating to that inode.
///
/// We implement conversion from [`INodeNo`] to [`u64`] but not vice-versa because
/// not all [`u64`]s are valid [`INodeNo`]s, but the reverse is true.  So to produce
/// a [`INodeNo`] from a [`u64`] we must be explicit.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))]
pub struct INodeNo(pub u64);
impl From<INodeNo> for u64 {
    fn from(fh: INodeNo) -> Self {
        fh.0
    }
}

/// A newtype for file handles
///
/// This corresponds to a single file description in a client program.  These
/// are generated by the filesystem implementation in replies to [`Open`],
/// [`OpenDir`] and [`Create`] requests.  It's used as a correlation id across
/// [`Read`], [`Write`], [`FSync`], [`IoCtl`], [`Poll`], [`FAllocate`], [`ReadDir`],
/// [`FSyncDir`], [`GetLk`], [`SetLk`], [`SetLkW`], [`ReadDirPlus`], [`Lseek`] and
/// [`CopyFileRange`] requests.
///
/// A filesystem implementation may store arbitrary data as the [`FileHandle`], as
/// long as it fits into 64-bits and doesn't need to change for over the lifetime
/// of the [`FileHandle`].  Typically this might consist of an index into an array
/// of [`FileHandle`]s that the filesystem implementation maintains.
///
/// Filesystems may instead implement stateless file I/O and use `0` as the
/// [`FileHandle`] - although this makes it impossible to correctly implement
/// resumable [`ReadDir`] in the presence of mutable directories (see [`OpenDir`]).
///
/// Lifecycle
/// ---------
///
/// A [`FileHandle`] is owned by one or more file-descriptors (or memory
/// mappings) in the client program.  Multiple file descriptors can point to
/// the same [`FileHandle`], just as a single `INode` can have multiple
/// [`FileHandle`]s open at one time.  Every time a single file-descriptor is
/// closed a [`Flush`] request is made.  This gives filesystem implementations
/// an opportunity to return an error message from that `close()` call.  After
/// all the file-descriptors are closed that own a given [`FileHandle`] the
/// [`Release`]/[`ReleaseDir`] request will be made.  This is an opportunity for
/// the filesystem implementation to free any internal per-FileHandle data
/// structures it has allocated.
///
/// We implement conversion from `FileHandle` to[ `u64`] but not vice-versa because
/// not all [`u64`]s are valid `FileHandles`, but the reverse is true.  So to produce
/// a `FileHandle` from a [`u64`] we must be explicit.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))]
pub struct FileHandle(pub u64);

impl From<FileHandle> for u64 {
    fn from(fh: FileHandle) -> Self {
        fh.0
    }
}

/// A newtype for lock owners
///
/// TODO: Document lock lifecycle and how and when to implement file locking.
///
/// See [`Read`], [`Write`], [`Release`], [`Flush`], [`GetLk`], [`SetLk`], [`SetLkW`].
///
/// We implement conversion from [`LockOwner`] to [`u64`] but not vice-versa
/// because all `LockOwners` are valid [`u64`]s, but not vice-versa.  So to produce
/// a [`LockOwner`] from a [`u64`] we must be explicit.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))]
pub struct LockOwner(pub u64);

impl From<LockOwner> for u64 {
    fn from(fh: LockOwner) -> Self {
        fh.0
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Lock {
    // Unfortunately this can't be a std::ops::Range because Range is not Copy:
    // https://github.com/rust-lang/rfcs/issues/2848
    pub range: (u64, u64),
    // TODO: Make typ an enum
    pub typ: i32,
    pub pid: u32,
}
impl Lock {
    fn from_abi(x: &abi::fuse_file_lock) -> Lock {
        Lock {
            range: (x.start, x.end),
            typ: x.typ,
            pid: x.pid,
        }
    }
}

/// A newtype for ABI version
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))]
pub struct Version(pub u32, pub u32);
impl Version {
    pub fn major(&self) -> u32 {
        self.0
    }
    pub fn minor(&self) -> u32 {
        self.1
    }
}
impl Display for Version {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}.{}", self.0, self.1)
    }
}

/// Represents a filename in a directory
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
pub struct FilenameInDir<'a> {
    /// The Inode number of the directory
    pub dir: INodeNo,
    /// Name of the file. This refers to a name directly in this directory, rather than any
    /// subdirectory so is guaranteed not to contain '\0' or '/'.  It may be literally "." or ".."
    /// however.
    pub name: &'a Path,
}

impl fmt::Display for RequestError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            RequestError::ShortReadHeader(len) => write!(
                f,
                "Short read of FUSE request header ({len} < {})",
                mem::size_of::<fuse_in_header>()
            ),
            RequestError::UnknownOperation(opcode) => write!(f, "Unknown FUSE opcode ({opcode})"),
            RequestError::ShortRead(len, total) => {
                write!(f, "Short read of FUSE request ({len} < {total})")
            }
            RequestError::InsufficientData => write!(f, "Insufficient argument data"),
        }
    }
}

impl error::Error for RequestError {}
pub trait Request: Sized {
    /// Returns the unique identifier of this request.
    ///
    /// The FUSE kernel driver assigns a unique id to every concurrent request. This allows to
    /// distinguish between multiple concurrent requests. The unique id of a request may be
    /// reused in later requests after it has completed.
    fn unique(&self) -> RequestId;

    /// Returns the node id of the inode this request is targeted to.
    fn nodeid(&self) -> INodeNo;

    /// Returns the UID that the process that triggered this request runs under.
    fn uid(&self) -> u32;

    /// Returns the GID that the process that triggered this request runs under.
    fn gid(&self) -> u32;

    /// Returns the PID of the process that triggered this request.
    fn pid(&self) -> u32;

    /// Create an error response for this Request
    fn reply_err(&self, errno: Errno) -> Response<'_> {
        Response::new_error(errno)
    }
}

macro_rules! impl_request {
    ($structname: ty) => {
        impl<'a> super::Request for $structname {
            #[inline]
            fn unique(&self) -> RequestId {
                RequestId(self.header.unique)
            }

            #[inline]
            fn nodeid(&self) -> INodeNo {
                INodeNo(self.header.nodeid)
            }

            #[inline]
            fn uid(&self) -> u32 {
                self.header.uid
            }

            #[inline]
            fn gid(&self) -> u32 {
                self.header.gid
            }

            #[inline]
            fn pid(&self) -> u32 {
                self.header.pid
            }
        }
    };
}

mod op {
    use crate::ll::Response;

    use super::{
        super::{TimeOrNow, argument::ArgumentIterator},
        FilenameInDir, Request,
    };
    use super::{
        FileHandle, INodeNo, Lock, LockOwner, Operation, RequestId, abi::consts::*, abi::*,
    };
    use std::{
        convert::TryInto,
        ffi::OsStr,
        fmt::Display,
        num::NonZeroU32,
        path::Path,
        time::{Duration, SystemTime},
    };
    use zerocopy::IntoBytes;

    /// Look up a directory entry by name and get its attributes.
    ///
    /// Implementations allocate and assign [`INodeNo`]s in this request.  Learn more
    /// about `INode` lifecycle and the relationship between [`Lookup`] and [`Forget`] in the
    /// documentation for [`INodeNo`].
    #[derive(Debug)]
    pub struct Lookup<'a> {
        header: &'a fuse_in_header,
        name: &'a OsStr,
    }
    impl_request!(Lookup<'_>);
    impl<'a> Lookup<'a> {
        pub fn name(&self) -> &'a Path {
            self.name.as_ref()
        }
    }
    /// Forget about an inode.
    ///
    /// The `nlookup` parameter indicates the number of lookups previously performed on
    /// this inode. If the filesystem implements inode lifetimes, it is recommended that
    /// inodes acquire a single reference on each lookup, and lose nlookup references on
    /// each forget. The filesystem may ignore forget calls, if the inodes don't need to
    /// have a limited lifetime.
    ///
    /// Learn more about `INode` lifecycle in the documentation for [`INodeNo`].
    ///
    /// On unmount it is not guaranteed, that all referenced inodes will receive a forget
    /// message.
    #[derive(Debug)]
    pub struct Forget<'a> {
        header: &'a fuse_in_header,
        arg: &'a fuse_forget_in,
    }
    impl_request!(Forget<'_>);
    impl Forget<'_> {
        /// The number of lookups previously performed on this inode
        pub fn nlookup(&self) -> u64 {
            self.arg.nlookup
        }
    }

    /// Get file attributes.
    #[derive(Debug)]
    pub struct GetAttr<'a> {
        header: &'a fuse_in_header,

        arg: &'a fuse_getattr_in,
    }
    impl_request!(GetAttr<'_>);

    impl GetAttr<'_> {
        pub fn file_handle(&self) -> Option<FileHandle> {
            if self.arg.getattr_flags & crate::FUSE_GETATTR_FH != 0 {
                Some(FileHandle(self.arg.fh))
            } else {
                None
            }
        }
    }

    /// Set file attributes.
    #[derive(Debug)]
    pub struct SetAttr<'a> {
        header: &'a fuse_in_header,
        arg: &'a fuse_setattr_in,
    }
    impl_request!(SetAttr<'_>);
    impl SetAttr<'_> {
        pub fn mode(&self) -> Option<u32> {
            match self.arg.valid & FATTR_MODE {
                0 => None,
                _ => Some(self.arg.mode),
            }
        }
        pub fn uid(&self) -> Option<u32> {
            match self.arg.valid & FATTR_UID {
                0 => None,
                _ => Some(self.arg.uid),
            }
        }
        pub fn gid(&self) -> Option<u32> {
            match self.arg.valid & FATTR_GID {
                0 => None,
                _ => Some(self.arg.gid),
            }
        }
        pub fn size(&self) -> Option<u64> {
            match self.arg.valid & FATTR_SIZE {
                0 => None,
                _ => Some(self.arg.size),
            }
        }
        pub fn atime(&self) -> Option<TimeOrNow> {
            match self.arg.valid & FATTR_ATIME {
                0 => None,
                _ => Some(if self.arg.atime_now() {
                    TimeOrNow::Now
                } else {
                    TimeOrNow::SpecificTime(system_time_from_time(
                        self.arg.atime,
                        self.arg.atimensec,
                    ))
                }),
            }
        }
        pub fn mtime(&self) -> Option<TimeOrNow> {
            match self.arg.valid & FATTR_MTIME {
                0 => None,
                _ => Some(if self.arg.mtime_now() {
                    TimeOrNow::Now
                } else {
                    TimeOrNow::SpecificTime(system_time_from_time(
                        self.arg.mtime,
                        self.arg.mtimensec,
                    ))
                }),
            }
        }
        pub fn ctime(&self) -> Option<SystemTime> {
            #[cfg(feature = "abi-7-23")]
            match self.arg.valid & FATTR_CTIME {
                0 => None,
                _ => Some(system_time_from_time(self.arg.ctime, self.arg.ctimensec)),
            }
            #[cfg(not(feature = "abi-7-23"))]
            None
        }
        /// The value set by the [`Open`] method. See [`FileHandle`].
        ///
        /// This will only be set if the user passed a file-descriptor to set the
        /// attributes - i.e. they used [`libc::fchmod`] rather than [`libc::chmod`].
        pub fn file_handle(&self) -> Option<FileHandle> {
            match self.arg.valid & FATTR_FH {
                0 => None,
                _ => Some(FileHandle(self.arg.fh)),
            }
        }
        pub fn crtime(&self) -> Option<SystemTime> {
            #[cfg(target_os = "macos")]
            match self.arg.valid & FATTR_CRTIME {
                0 => None,
                // During certain operation, macOS use some helper that send request to the mountpoint with `crtime` set to 0xffffffff83da4f80.
                // That value correspond to `-2_082_844_800u64` which is the difference between the date 1904-01-01 and 1970-01-01 because macOS epoch start at 1904 and not 1970.
                // https://github.com/macfuse/macfuse/issues/1042
                _ if self.arg.crtime == 0xffffffff83da4f80 => None,
                _ => Some(
                    SystemTime::UNIX_EPOCH + Duration::new(self.arg.crtime, self.arg.crtimensec),
                ),
            }
            #[cfg(not(target_os = "macos"))]
            None
        }
        pub fn chgtime(&self) -> Option<SystemTime> {
            #[cfg(target_os = "macos")]
            match self.arg.valid & FATTR_CHGTIME {
                0 => None,
                _ => Some(
                    SystemTime::UNIX_EPOCH + Duration::new(self.arg.chgtime, self.arg.chgtimensec),
                ),
            }
            #[cfg(not(target_os = "macos"))]
            None
        }
        pub fn bkuptime(&self) -> Option<SystemTime> {
            #[cfg(target_os = "macos")]
            match self.arg.valid & FATTR_BKUPTIME {
                0 => None,
                _ => Some(
                    SystemTime::UNIX_EPOCH
                        + Duration::new(self.arg.bkuptime, self.arg.bkuptimensec),
                ),
            }
            #[cfg(not(target_os = "macos"))]
            None
        }
        pub fn flags(&self) -> Option<u32> {
            #[cfg(target_os = "macos")]
            match self.arg.valid & FATTR_FLAGS {
                0 => None,
                _ => Some(self.arg.flags),
            }
            #[cfg(not(target_os = "macos"))]
            None
        }

        // TODO: Why does *set*attr want to have an attr response?
    }
    impl Display for SetAttr<'_> {
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            write!(
                f,
                "SETATTR mode: {:?}, uid: {:?}, gid: {:?}, size: {:?}, atime: {:?}, \
                mtime: {:?}, ctime: {:?}, file_handle: {:?}, crtime: {:?}, chgtime: {:?}, \
                bkuptime: {:?}, flags: {:?}",
                self.mode(),
                self.uid(),
                self.gid(),
                self.size(),
                self.atime(),
                self.mtime(),
                self.ctime(),
                self.file_handle(),
                self.crtime(),
                self.chgtime(),
                self.bkuptime(),
                self.flags()
            )
        }
    }

    /// Read symbolic link.
    #[derive(Debug)]
    pub struct ReadLink<'a> {
        header: &'a fuse_in_header,
    }
    impl_request!(ReadLink<'_>);

    /// Create a symbolic link.
    #[derive(Debug)]
    pub struct SymLink<'a> {
        header: &'a fuse_in_header,
        target: &'a Path,
        link_name: &'a Path,
    }
    impl_request!(SymLink<'_>);
    impl<'a> SymLink<'a> {
        pub fn target(&self) -> &'a Path {
            self.target
        }
        pub fn link_name(&self) -> &'a Path {
            self.link_name
        }
    }

    /// Create file node.
    /// Create a regular file, character device, block device, fifo or socket node.
    #[derive(Debug)]
    pub struct MkNod<'a> {
        header: &'a fuse_in_header,
        arg: &'a fuse_mknod_in,
        name: &'a Path,
    }
    impl_request!(MkNod<'_>);
    impl<'a> MkNod<'a> {
        pub fn name(&self) -> &'a Path {
            self.name
        }
        pub fn mode(&self) -> u32 {
            self.arg.mode
        }
        pub fn umask(&self) -> u32 {
            self.arg.umask
        }
        pub fn rdev(&self) -> u32 {
            self.arg.rdev
        }
    }

    /// Create a directory.
    #[derive(Debug)]
    pub struct MkDir<'a> {
        header: &'a fuse_in_header,
        arg: &'a fuse_mkdir_in,
        name: &'a Path,
    }
    impl_request!(MkDir<'_>);
    impl<'a> MkDir<'a> {
        pub fn name(&self) -> &'a Path {
            self.name
        }
        pub fn mode(&self) -> u32 {
            self.arg.mode
        }
        pub fn umask(&self) -> u32 {
            self.arg.umask
        }
    }

    /// Remove a file.
    #[derive(Debug)]
    pub struct Unlink<'a> {
        header: &'a fuse_in_header,
        name: &'a Path,
    }
    impl_request!(Unlink<'_>);
    impl<'a> Unlink<'a> {
        pub fn name(&self) -> &'a Path {
            self.name
        }
    }

    /// Remove a directory.
    #[derive(Debug)]
    pub struct RmDir<'a> {
        header: &'a fuse_in_header,
        pub name: &'a Path,
    }
    impl_request!(RmDir<'_>);
    impl<'a> RmDir<'a> {
        pub fn name(&self) -> &'a Path {
            self.name
        }
    }

    /// Rename a file.
    #[derive(Debug)]
    pub struct Rename<'a> {
        header: &'a fuse_in_header,
        arg: &'a fuse_rename_in,
        name: &'a Path,
        newname: &'a Path,
    }
    impl_request!(Rename<'_>);
    impl<'a> Rename<'a> {
        pub fn src(&self) -> FilenameInDir<'a> {
            FilenameInDir::<'a> {
                dir: self.nodeid(),
                name: self.name,
            }
        }
        pub fn dest(&self) -> FilenameInDir<'a> {
            FilenameInDir::<'a> {
                dir: INodeNo(self.arg.newdir),
                name: self.newname,
            }
        }
    }

    /// Create a hard link.
    #[derive(Debug)]
    pub struct Link<'a> {
        header: &'a fuse_in_header,
        arg: &'a fuse_link_in,
        name: &'a Path,
    }
    impl_request!(Link<'_>);
    impl<'a> Link<'a> {
        /// This is the inode no of the file to be linked.  The inode number in
        /// the fuse header is of the directory that it will be linked into.
        pub fn inode_no(&self) -> INodeNo {
            INodeNo(self.arg.oldnodeid)
        }
        pub fn dest(&self) -> FilenameInDir<'a> {
            FilenameInDir::<'a> {
                dir: self.nodeid(),
                name: self.name,
            }
        }
    }

    /// Open a file.
    ///
    /// Open flags (with the exception of `O_CREAT`, `O_EXCL`, `O_NOCTTY` and `O_TRUNC`) are
    /// available in flags. Filesystem may store an arbitrary file handle (pointer, index,
    /// etc) in fh, and use this in other all other file operations (read, write, flush,
    /// release, fsync). Filesystem may also implement stateless file I/O and not store
    /// anything in fh. There are also some flags (`direct_io`, `keep_cache`) which the
    /// filesystem may set, to change the way the file is opened. See `fuse_file_info`
    /// structure in <`fuse_common.h`> for more details.
    #[derive(Debug)]
    pub struct Open<'a> {
        header: &'a fuse_in_header,
        arg: &'a fuse_open_in,
    }
    impl_request!(Open<'_>);
    impl Open<'_> {
        pub fn flags(&self) -> i32 {
            self.arg.flags
        }
    }

    /// Read data.
    ///
    /// Read should send exactly the number of bytes requested except on EOF or error,
    /// otherwise the rest of the data will be substituted with zeroes. An exception to
    /// this is when the file has been opened in `direct_io` mode, in which case the
    /// return value of the read system call will reflect the return value of this
    /// operation.
    #[derive(Debug)]
    pub struct Read<'a> {
        header: &'a fuse_in_header,
        arg: &'a fuse_read_in,
    }
    impl_request!(Read<'_>);
    impl Read<'_> {
        /// The value set by the [Open] method.
        pub fn file_handle(&self) -> FileHandle {
            FileHandle(self.arg.fh)
        }
        pub fn offset(&self) -> i64 {
            self.arg.offset
        }
        pub fn size(&self) -> u32 {
            self.arg.size
        }
        /// Only supported with ABI >= 7.9
        pub fn lock_owner(&self) -> Option<LockOwner> {
            if self.arg.read_flags & FUSE_READ_LOCKOWNER != 0 {
                Some(LockOwner(self.arg.lock_owner))
            } else {
                None
            }
        }
        /// The file flags, such as `O_SYNC`. Only supported with ABI >= 7.9
        pub fn flags(&self) -> i32 {
            self.arg.flags
        }
    }

    /// Write data.
    ///
    /// Write should return exactly the number of bytes requested except on error. An
    /// exception to this is when the file has been opened in `direct_io` mode, in
    /// which case the return value of the write system call will reflect the return
    /// value of this operation.
    #[derive(Debug)]
    pub struct Write<'a> {
        header: &'a fuse_in_header,
        arg: &'a fuse_write_in,
        data: &'a [u8],
    }
    impl_request!(Write<'_>);
    impl<'a> Write<'a> {
        /// The value set by the [Open] method.
        pub fn file_handle(&self) -> FileHandle {
            FileHandle(self.arg.fh)
        }
        pub fn offset(&self) -> i64 {
            self.arg.offset
        }
        pub fn data(&self) -> &'a [u8] {
            self.data
        }
        /// Will contain `FUSE_WRITE_CACHE`, if this write is from the page cache. If set,
        /// the pid, uid, gid, and fh may not match the value that would have been sent if write caching
        /// is disabled
        ///
        /// TODO: `WriteFlags` type or remove this
        pub fn write_flags(&self) -> u32 {
            self.arg.write_flags
        }
        /// `lock_owner`: only supported with ABI >= 7.9
        pub fn lock_owner(&self) -> Option<LockOwner> {
            if self.arg.write_flags & FUSE_WRITE_LOCKOWNER != 0 {
                Some(LockOwner(self.arg.lock_owner))
            } else {
                None
            }
        }
        /// flags: these are the file flags, such as `O_SYNC`. Only supported with ABI >= 7.9
        /// TODO: Make a Flags type specifying valid values
        pub fn flags(&self) -> i32 {
            self.arg.flags
        }
    }

    /// Get file system statistics.
    #[derive(Debug)]
    pub struct StatFs<'a> {
        header: &'a fuse_in_header,
    }
    impl_request!(StatFs<'_>);

    /// Release an open file.
    ///
    /// Release is called when there are no more references to an open file: all file
    /// descriptors are closed and all memory mappings are unmapped. For every [`Open`]
    /// call there will be exactly one release call. The filesystem may reply with an
    /// error, but error values are not returned to `close()` or `munmap()` which
    /// triggered the release.
    #[derive(Debug)]
    pub struct Release<'a> {
        header: &'a fuse_in_header,
        arg: &'a fuse_release_in,
    }
    impl_request!(Release<'_>);
    impl Release<'_> {
        pub fn flush(&self) -> bool {
            self.arg.release_flags & FUSE_RELEASE_FLUSH != 0
        }
        /// The value set by the [`Open`] method.
        pub fn file_handle(&self) -> FileHandle {
            FileHandle(self.arg.fh)
        }
        /// The same flags as for open.
        /// TODO: Document what flags are valid, or remove this
        pub fn flags(&self) -> i32 {
            self.arg.flags
        }
        pub fn lock_owner(&self) -> Option<LockOwner> {
            if self.arg.release_flags & FUSE_RELEASE_FLOCK_UNLOCK != 0 {
                Some(LockOwner(self.arg.lock_owner))
            } else {
                None
            }
        }
    }

    /// Synchronize file contents.
    #[derive(Debug)]
    pub struct FSync<'a> {
        header: &'a fuse_in_header,
        arg: &'a fuse_fsync_in,
    }
    impl_request!(FSync<'a>);
    impl FSync<'_> {
        /// The value set by the [`Open`] method.
        pub fn file_handle(&self) -> FileHandle {
            FileHandle(self.arg.fh)
        }
        /// If set only the user data should be flushed, not the meta data.
        pub fn fdatasync(&self) -> bool {
            self.arg.fsync_flags & consts::FUSE_FSYNC_FDATASYNC != 0
        }
    }

    /// Set an extended attribute.
    #[derive(Debug)]
    pub struct SetXAttr<'a> {
        header: &'a fuse_in_header,
        arg: &'a fuse_setxattr_in,
        name: &'a OsStr,
        value: &'a [u8],
    }
    impl_request!(SetXAttr<'a>);
    impl<'a> SetXAttr<'a> {
        pub fn name(&self) -> &'a OsStr {
            self.name
        }
        pub fn value(&self) -> &'a [u8] {
            self.value
        }
        // TODO: Document what are valid flags
        pub fn flags(&self) -> i32 {
            self.arg.flags
        }
        /// This will always be 0 except on `MacOS`.  It's recommended that
        /// implementations return EINVAL if this is not 0.
        pub fn position(&self) -> u32 {
            #[cfg(target_os = "macos")]
            return self.arg.position;
            #[cfg(not(target_os = "macos"))]
            0
        }
    }

    /// Get an extended attribute.
    ///
    /// If the requested `XAttr` doesn't exist, return [`Err(Errno::NO_XATTR)`] which will
    /// map to the right platform-specific error code.
    #[derive(Debug)]
    pub struct GetXAttr<'a> {
        header: &'a fuse_in_header,
        arg: &'a fuse_getxattr_in,
        name: &'a OsStr,
    }
    impl_request!(GetXAttr<'a>);

    /// Type for [`GetXAttrSizeEnum::GetSize`].
    ///
    /// Represents a request from the user to get the size of the data stored in the `XAttr`.
    #[derive(Debug)]
    pub struct GetXAttrSize();

    #[derive(Debug)]
    /// Return type for [`GetXAttr::size`].
    pub enum GetXAttrSizeEnum {
        /// User is requesting the size of the data stored in the `XAttr`
        GetSize(GetXAttrSize),
        /// User is requesting the data stored in the `XAttr`.  If the data will fit
        /// in this number of bytes it should be returned, otherwise return [`Err(Errno::ERANGE)`].
        #[allow(dead_code)]
        Size(NonZeroU32),
    }
    impl<'a> GetXAttr<'a> {
        /// Name of the `XAttr`
        pub fn name(&self) -> &'a OsStr {
            self.name
        }
        /// See [`GetXAttrSizeEnum`].
        ///
        /// You only need to check this value as an optimisation where there's a
        /// cost difference between checking the size of the data stored in an `XAttr`
        /// and actually providing the data.  Otherwise just call [`reply()`] with the
        /// data and it will do the right thing.
        pub fn size(&self) -> GetXAttrSizeEnum {
            let s: Result<NonZeroU32, _> = self.arg.size.try_into();
            match s {
                Ok(s) => GetXAttrSizeEnum::Size(s),
                Err(_) => GetXAttrSizeEnum::GetSize(GetXAttrSize()),
            }
        }
        /// The size of the buffer the user has allocated to store the `XAttr` value.
        pub(crate) fn size_u32(&self) -> u32 {
            self.arg.size
        }
    }

    /// List extended attribute names.
    #[derive(Debug)]
    pub struct ListXAttr<'a> {
        header: &'a fuse_in_header,
        arg: &'a fuse_getxattr_in,
    }
    impl_request!(ListXAttr<'a>);
    impl ListXAttr<'_> {
        /// The size of the buffer the caller has allocated to receive the list of
        /// `XAttrs`.  If this is 0 the user is just probing to find how much space is
        /// required to fit the whole list.
        ///
        /// You don't need to worry about this except as an optimisation.
        pub fn size(&self) -> u32 {
            self.arg.size
        }
    }

    /// Remove an extended attribute.
    ///
    /// Return [`Err(Errno::NO_XATTR)`] if the xattr doesn't exist
    /// Return [`Err(Errno::ENOTSUP)`] if this filesystem doesn't support `XAttrs`
    #[derive(Debug)]
    pub struct RemoveXAttr<'a> {
        header: &'a fuse_in_header,
        name: &'a OsStr,
    }
    impl_request!(RemoveXAttr<'a>);
    impl<'a> RemoveXAttr<'a> {
        /// Name of the `XAttr` to remove
        pub fn name(&self) -> &'a OsStr {
            self.name
        }
    }

    /// Flush method.
    ///
    /// This is called on each `close()` of the opened file. Since file descriptors can
    /// be duplicated (dup, dup2, fork), for one open call there may be many flush
    /// calls. Filesystems shouldn't assume that flush will always be called after some
    /// writes, or that if will be called at all.
    ///
    /// NOTE: the name of the method is misleading, since (unlike `fsync`) the filesystem
    /// is not forced to flush pending writes. One reason to flush data, is if the
    /// filesystem wants to return write errors. If the filesystem supports file locking
    /// operations (`setlk`, `getlk`) it should remove all locks belonging to `lock_owner`.
    #[derive(Debug)]
    pub struct Flush<'a> {
        header: &'a fuse_in_header,
        arg: &'a fuse_flush_in,
    }
    impl_request!(Flush<'a>);
    impl Flush<'_> {
        /// The value set by the open method
        pub fn file_handle(&self) -> FileHandle {
            FileHandle(self.arg.fh)
        }
        pub fn lock_owner(&self) -> LockOwner {
            LockOwner(self.arg.lock_owner)
        }
    }

    #[derive(Debug)]
    pub struct Init<'a> {
        header: &'a fuse_in_header,
        arg: &'a fuse_init_in,
    }
    impl_request!(Init<'a>);
    impl<'a> Init<'a> {
        pub fn capabilities(&self) -> u64 {
            #[cfg(feature = "abi-7-36")]
            if self.arg.flags & (FUSE_INIT_EXT as u32) != 0 {
                return u64::from(self.arg.flags) | (u64::from(self.arg.flags2) << 32);
            }
            u64::from(self.arg.flags)
        }
        pub fn max_readahead(&self) -> u32 {
            self.arg.max_readahead
        }
        pub fn version(&self) -> super::Version {
            super::Version(self.arg.major, self.arg.minor)
        }

        pub fn reply(&self, config: &crate::KernelConfig) -> Response<'a> {
            let flags = self.capabilities() & config.requested; // use requested features and reported as capable

            let init = fuse_init_out {
                major: FUSE_KERNEL_VERSION,
                minor: FUSE_KERNEL_MINOR_VERSION,
                max_readahead: config.max_readahead,
                #[cfg(not(feature = "abi-7-36"))]
                flags: flags as u32,
                #[cfg(feature = "abi-7-36")]
                flags: (flags | FUSE_INIT_EXT) as u32,
                max_background: config.max_background,
                congestion_threshold: config.congestion_threshold(),
                max_write: config.max_write,
                #[cfg(feature = "abi-7-23")]
                time_gran: config.time_gran.as_nanos() as u32,
                #[cfg(all(feature = "abi-7-23", not(feature = "abi-7-28")))]
                reserved: [0; 9],
                #[cfg(feature = "abi-7-28")]
                max_pages: config.max_pages(),
                #[cfg(feature = "abi-7-28")]
                unused2: 0,
                #[cfg(all(feature = "abi-7-28", not(feature = "abi-7-36")))]
                reserved: [0; 8],
                #[cfg(feature = "abi-7-36")]
                flags2: (flags >> 32) as u32,
                #[cfg(all(feature = "abi-7-36", not(feature = "abi-7-40")))]
                reserved: [0; 7],
                #[cfg(feature = "abi-7-40")]
                max_stack_depth: config.max_stack_depth,
                #[cfg(feature = "abi-7-40")]
                reserved: [0; 6],
            };
            Response::new_data(init.as_bytes())
        }
    }

    /// Open a directory.
    ///
    /// Filesystem may store an arbitrary file handle (pointer, index, etc) in fh, and
    /// use this in other all other directory stream operations ([`ReadDir`], [`ReleaseDir`],
    /// [`FSyncDir`]). Filesystem may also implement stateless directory I/O and not store
    /// anything in `fh`, though that makes it impossible to implement standard conforming
    /// directory stream operations in case the contents of the directory can change
    /// between [`OpenDir`] and [`ReleaseDir`].
    ///
    /// TODO: Document how to implement "standard conforming directory stream operations"
    #[derive(Debug)]
    pub struct OpenDir<'a> {
        header: &'a fuse_in_header,
        arg: &'a fuse_open_in,
    }
    impl_request!(OpenDir<'a>);
    impl OpenDir<'_> {
        /// Flags as passed to open
        pub fn flags(&self) -> i32 {
            self.arg.flags
        }
    }

    /// Read directory.
    #[derive(Debug)]
    pub struct ReadDir<'a> {
        header: &'a fuse_in_header,
        arg: &'a fuse_read_in,
    }
    impl_request!(ReadDir<'a>);
    impl ReadDir<'_> {
        /// The value set by the [`OpenDir`] method.
        pub fn file_handle(&self) -> FileHandle {
            FileHandle(self.arg.fh)
        }
        pub fn offset(&self) -> i64 {
            self.arg.offset
        }
        pub fn size(&self) -> u32 {
            self.arg.size
        }
    }

    /// Release an open directory.
    ///
    /// For every [`OpenDir`] call there will be exactly one [`ReleaseDir`] call.
    #[derive(Debug)]
    pub struct ReleaseDir<'a> {
        header: &'a fuse_in_header,
        arg: &'a fuse_release_in,
    }
    impl_request!(ReleaseDir<'a>);
    impl ReleaseDir<'_> {
        /// The value set by the [`OpenDir`] method.
        pub fn file_handle(&self) -> FileHandle {
            FileHandle(self.arg.fh)
        }
        pub fn flush(&self) -> bool {
            self.arg.release_flags & consts::FUSE_RELEASE_FLUSH != 0
        }
        pub fn lock_owner(&self) -> Option<LockOwner> {
            if self.arg.release_flags & FUSE_RELEASE_FLOCK_UNLOCK != 0 {
                Some(LockOwner(self.arg.lock_owner))
            } else {
                None
            }
        }
        /// TODO: Document what values this may take
        pub fn flags(&self) -> i32 {
            self.arg.flags
        }
    }

    /// Synchronize directory contents.
    #[derive(Debug)]
    pub struct FSyncDir<'a> {
        header: &'a fuse_in_header,
        arg: &'a fuse_fsync_in,
    }
    impl_request!(FSyncDir<'a>);
    impl FSyncDir<'_> {
        /// The value set by the [`OpenDir`] method. See [`FileHandle`].
        pub fn file_handle(&self) -> FileHandle {
            FileHandle(self.arg.fh)
        }
        /// If set, then only the directory contents should be flushed, not the meta data.
        pub fn fdatasync(&self) -> bool {
            self.arg.fsync_flags & consts::FUSE_FSYNC_FDATASYNC != 0
        }
    }

    /// Test for a POSIX file lock.
    #[derive(Debug)]
    pub struct GetLk<'a> {
        header: &'a fuse_in_header,
        arg: &'a fuse_lk_in,
    }
    impl_request!(GetLk<'a>);
    impl GetLk<'_> {
        /// The value set by the [`Open`] method. See [`FileHandle`].
        pub fn file_handle(&self) -> FileHandle {
            FileHandle(self.arg.fh)
        }
        pub fn lock(&self) -> Lock {
            Lock::from_abi(&self.arg.lk)
        }
        pub fn lock_owner(&self) -> LockOwner {
            LockOwner(self.arg.owner)
        }
    }

    /// Acquire, modify or release a POSIX file lock.
    ///
    /// For POSIX threads (NPTL) there's a 1-1 relation between pid and owner, but
    /// otherwise this is not always the case.  For checking lock ownership,
    /// 'fi->owner' must be used. The `l_pid` field in 'struct `flock`' should only be
    /// used to fill in this field in `getlk()`. Note: if the locking methods are not
    /// implemented, the kernel will still allow file locking to work locally.
    /// Hence these are only interesting for network filesystems and similar.
    #[derive(Debug)]
    pub struct SetLk<'a> {
        header: &'a fuse_in_header,
        arg: &'a fuse_lk_in,
    }
    impl_request!(SetLk<'a>);
    impl SetLk<'_> {
        /// The value set by the [`Open`] method. See [`FileHandle`].
        pub fn file_handle(&self) -> FileHandle {
            FileHandle(self.arg.fh)
        }
        pub fn lock(&self) -> Lock {
            Lock::from_abi(&self.arg.lk)
        }
        pub fn lock_owner(&self) -> LockOwner {
            LockOwner(self.arg.owner)
        }
    }
    #[derive(Debug)]
    pub struct SetLkW<'a> {
        header: &'a fuse_in_header,
        arg: &'a fuse_lk_in,
    }
    impl_request!(SetLkW<'a>);
    impl SetLkW<'_> {
        /// The value set by the [`Open`] method. See [`FileHandle`].
        pub fn file_handle(&self) -> FileHandle {
            FileHandle(self.arg.fh)
        }
        pub fn lock(&self) -> Lock {
            Lock::from_abi(&self.arg.lk)
        }
        pub fn lock_owner(&self) -> LockOwner {
            LockOwner(self.arg.owner)
        }
    }

    /// Check file access permissions.
    ///
    /// This will be called for the `access()` system call. If the `default_permissions`
    /// mount option is given, this method is not called.
    #[derive(Debug)]
    pub struct Access<'a> {
        header: &'a fuse_in_header,
        arg: &'a fuse_access_in,
    }
    impl_request!(Access<'a>);
    impl Access<'_> {
        pub fn mask(&self) -> i32 {
            self.arg.mask
        }
    }

    /// Create and open a file.
    ///
    /// If the file does not exist, first create it with the specified mode, and then
    /// open it. Open flags (with the exception of `O_NOCTTY`) are available in flags.
    /// Filesystem may store an arbitrary file handle (pointer, index, etc) in fh,
    /// and use this in other all other file operations ([`Read`], [`Write`], [`Flush`], [`Release`],
    /// [`FSync`]). There are also some flags (`direct_io`, `keep_cache`) which the
    /// filesystem may set, to change the way the file is opened. See `fuse_file_info`
    /// structure in <`fuse_common.h`> for more details. If this method is not
    /// implemented or under Linux kernel versions earlier than 2.6.15, the [`MkNod`]
    /// and [`Open`] methods will be called instead.
    #[derive(Debug)]
    pub struct Create<'a> {
        header: &'a fuse_in_header,
        arg: &'a fuse_create_in,
        name: &'a Path,
    }
    impl_request!(Create<'a>);
    impl<'a> Create<'a> {
        pub fn name(&self) -> &'a Path {
            self.name
        }
        pub fn mode(&self) -> u32 {
            self.arg.mode
        }
        /// Flags as passed to the `create()` call
        pub fn flags(&self) -> i32 {
            self.arg.flags
        }
        pub fn umask(&self) -> u32 {
            self.arg.umask
        }
    }

    /// If a process issuing a FUSE filesystem request is interrupted, the
    /// following will happen:
    ///
    ///   1) If the request is not yet sent to userspace AND the signal is
    ///      fatal (SIGKILL or unhandled fatal signal), then the request is
    ///      dequeued and returns immediately.
    ///
    ///   2) If the request is not yet sent to userspace AND the signal is not
    ///      fatal, then an 'interrupted' flag is set for the request.  When
    ///      the request has been successfully transferred to userspace and
    ///      this flag is set, an INTERRUPT request is queued.
    ///
    ///   3) If the request is already sent to userspace, then an INTERRUPT
    ///      request is queued.
    ///
    /// [`Interrupt`] requests take precedence over other requests, so the
    /// userspace filesystem will receive queued [`Interrupt`]s before any others.
    ///
    /// The userspace filesystem may ignore the [`Interrupt`] requests entirely,
    /// or may honor them by sending a reply to the **original** request, with
    /// the error set to [`Errno::EINTR`].
    ///
    /// It is also possible that there's a race between processing the
    /// original request and its [`Interrupt`] request.  There are two
    /// possibilities:
    ///
    /// 1. The [`Interrupt`] request is processed before the original request is
    ///    processed
    ///
    /// 2. The [`Interrupt`] request is processed after the original request has
    ///    been answered
    ///
    /// If the filesystem cannot find the original request, it should wait for
    /// some timeout and/or a number of new requests to arrive, after which it
    /// should reply to the [`Interrupt`] request with an [`Errno::EAGAIN`] error.
    /// In case (1) the [`Interrupt`] request will be requeued.  In case (2) the
    /// [`Interrupt`] reply will be ignored.
    #[derive(Debug)]
    pub struct Interrupt<'a> {
        header: &'a fuse_in_header,
        arg: &'a fuse_interrupt_in,
    }
    impl_request!(Interrupt<'a>);
    impl Interrupt<'_> {
        pub fn unique(&self) -> RequestId {
            RequestId(self.arg.unique)
        }
    }

    /// Map block index within file to block index within device.
    /// Note: This makes sense only for block device backed filesystems mounted
    /// with the `blkdev` option
    #[derive(Debug)]
    pub struct BMap<'a> {
        header: &'a fuse_in_header,
        arg: &'a fuse_bmap_in,
    }
    impl_request!(BMap<'a>);
    impl BMap<'_> {
        pub fn block_size(&self) -> u32 {
            self.arg.blocksize
        }
        pub fn block(&self) -> u64 {
            self.arg.block
        }
    }

    #[derive(Debug)]
    pub struct Destroy<'a> {
        header: &'a fuse_in_header,
    }
    impl_request!(Destroy<'a>);
    impl<'a> Destroy<'a> {
        pub fn reply(&self) -> Response<'a> {
            Response::new_empty()
        }
    }

    /// Control device
    #[derive(Debug)]
    pub struct IoCtl<'a> {
        header: &'a fuse_in_header,
        arg: &'a fuse_ioctl_in,
        data: &'a [u8],
    }
    impl_request!(IoCtl<'a>);
    impl IoCtl<'_> {
        pub fn in_data(&self) -> &[u8] {
            &self.data[..self.arg.in_size as usize]
        }
        pub fn unrestricted(&self) -> bool {
            self.arg.flags & consts::FUSE_IOCTL_UNRESTRICTED != 0
        }
        /// The value set by the [`Open`] method. See [`FileHandle`].
        pub fn file_handle(&self) -> FileHandle {
            FileHandle(self.arg.fh)
        }
        /// TODO: What are valid values here?
        pub fn flags(&self) -> u32 {
            self.arg.flags
        }
        /// TODO: What does this mean?
        pub fn command(&self) -> u32 {
            self.arg.cmd
        }
        pub fn out_size(&self) -> u32 {
            self.arg.out_size
        }
    }

    /// Poll.
    #[derive(Debug)]
    pub struct Poll<'a> {
        header: &'a fuse_in_header,
        arg: &'a fuse_poll_in,
    }
    impl_request!(Poll<'a>);
    impl Poll<'_> {
        /// The value set by the [`Open`] method. See [`FileHandle`].
        pub fn file_handle(&self) -> FileHandle {
            FileHandle(self.arg.fh)
        }

        /// The unique id used for the poll context by the kernel
        pub fn kernel_handle(&self) -> u64 {
            self.arg.kh
        }

        /// The requested poll events
        pub fn events(&self) -> u32 {
            #[cfg(feature = "abi-7-21")]
            return self.arg.events;
            #[cfg(not(feature = "abi-7-21"))]
            return 0;
        }

        /// The poll request's flags
        pub fn flags(&self) -> u32 {
            self.arg.flags
        }
    }

    /// `NotifyReply`.  TODO: currently unsupported by fuser
    #[derive(Debug)]
    pub struct NotifyReply<'a> {
        header: &'a fuse_in_header,
        #[allow(unused)]
        arg: &'a [u8],
    }
    impl_request!(NotifyReply<'a>);

    /// `BatchForget`: TODO: merge with Forget
    #[derive(Debug)]
    pub struct BatchForget<'a> {
        header: &'a fuse_in_header,
        #[allow(unused)]
        arg: &'a fuse_batch_forget_in,
        nodes: &'a [fuse_forget_one],
    }
    impl_request!(BatchForget<'a>);
    impl<'a> BatchForget<'a> {
        /// TODO: Don't return `fuse_forget_one`, this should be private
        pub fn nodes(&self) -> &'a [fuse_forget_one] {
            self.nodes
        }
    }

    /// Preallocate or deallocate space to a file
    ///
    /// Implementations should return EINVAL if offset or length are < 0
    #[cfg(feature = "abi-7-19")]
    #[derive(Debug)]
    pub struct FAllocate<'a> {
        header: &'a fuse_in_header,
        arg: &'a fuse_fallocate_in,
    }
    #[cfg(feature = "abi-7-19")]
    impl_request!(FAllocate<'a>);
    #[cfg(feature = "abi-7-19")]
    impl FAllocate<'_> {
        /// The value set by the [`Open`] method. See [`FileHandle`].
        pub fn file_handle(&self) -> FileHandle {
            FileHandle(self.arg.fh)
        }
        pub fn offset(&self) -> i64 {
            self.arg.offset
        }
        pub fn len(&self) -> i64 {
            self.arg.length
        }
        /// `mode` as passed to fallocate.  See `man 2 fallocate`
        pub fn mode(&self) -> i32 {
            self.arg.mode
        }
    }

    /// Read directory.
    ///
    /// TODO: Document when this is called rather than `ReadDir`
    #[cfg(feature = "abi-7-21")]
    #[derive(Debug)]
    pub struct ReadDirPlus<'a> {
        header: &'a fuse_in_header,
        arg: &'a fuse_read_in,
    }
    #[cfg(feature = "abi-7-21")]
    impl_request!(ReadDirPlus<'a>);
    #[cfg(feature = "abi-7-21")]
    impl ReadDirPlus<'_> {
        /// The value set by the [`Open`] method. See [`FileHandle`].
        pub fn file_handle(&self) -> FileHandle {
            FileHandle(self.arg.fh)
        }
        pub fn offset(&self) -> i64 {
            self.arg.offset
        }
        pub fn size(&self) -> u32 {
            self.arg.size
        }
    }

    /// Rename a file.
    ///
    /// TODO: Document the differences to [`Rename`] and [`Exchange`]
    #[cfg(feature = "abi-7-23")]
    #[derive(Debug)]
    pub struct Rename2<'a> {
        header: &'a fuse_in_header,
        arg: &'a fuse_rename2_in,
        name: &'a Path,
        newname: &'a Path,
        old_parent: INodeNo,
    }
    #[cfg(feature = "abi-7-23")]
    impl_request!(Rename2<'a>);
    #[cfg(feature = "abi-7-23")]
    impl<'a> Rename2<'a> {
        pub fn from(&self) -> FilenameInDir<'a> {
            FilenameInDir::<'a> {
                dir: self.old_parent,
                name: self.name,
            }
        }
        pub fn to(&self) -> FilenameInDir<'a> {
            FilenameInDir::<'a> {
                dir: INodeNo(self.arg.newdir),
                name: self.newname,
            }
        }
        /// Flags as passed to renameat2.  As of Linux 3.18 this is
        /// [`libc::RENAME_EXCHANGE`], [`libc::RENAME_NOREPLACE`] and
        /// [`libc::RENAME_WHITEOUT`].  If you don't handle a particular flag
        /// reply with an EINVAL error.
        ///
        /// TODO: Replace with enum/flags type
        pub fn flags(&self) -> u32 {
            self.arg.flags
        }
    }

    /// Reposition read/write file offset
    ///
    /// TODO: Document when you need to implement this.  Read and Write provide the offset anyway.
    #[cfg(feature = "abi-7-24")]
    #[derive(Debug)]
    pub struct Lseek<'a> {
        header: &'a fuse_in_header,
        arg: &'a fuse_lseek_in,
    }
    #[cfg(feature = "abi-7-24")]
    impl_request!(Lseek<'a>);
    #[cfg(feature = "abi-7-24")]
    impl Lseek<'_> {
        /// The value set by the [`Open`] method. See [`FileHandle`].
        pub fn file_handle(&self) -> FileHandle {
            FileHandle(self.arg.fh)
        }
        pub fn offset(&self) -> i64 {
            self.arg.offset
        }
        /// TODO: Make this return an enum
        pub fn whence(&self) -> i32 {
            self.arg.whence
        }
    }

    /// Copy the specified range from the source inode to the destination inode
    #[cfg(feature = "abi-7-28")]
    #[derive(Debug, Clone, Copy)]
    pub struct CopyFileRangeFile {
        pub inode: INodeNo,
        /// The value set by the [`Open`] method. See [`FileHandle`].
        pub file_handle: FileHandle,
        pub offset: i64,
    }
    #[cfg(feature = "abi-7-28")]
    #[derive(Debug)]
    pub struct CopyFileRange<'a> {
        header: &'a fuse_in_header,
        arg: &'a fuse_copy_file_range_in,
    }
    #[cfg(feature = "abi-7-28")]
    impl_request!(CopyFileRange<'a>);
    #[cfg(feature = "abi-7-28")]
    impl CopyFileRange<'_> {
        /// File and offset to copy data from
        pub fn src(&self) -> CopyFileRangeFile {
            CopyFileRangeFile {
                inode: self.nodeid(),
                file_handle: FileHandle(self.arg.fh_in),
                offset: self.arg.off_in,
            }
        }
        /// File and offset to copy data to
        pub fn dest(&self) -> CopyFileRangeFile {
            CopyFileRangeFile {
                inode: INodeNo(self.arg.nodeid_out),
                file_handle: FileHandle(self.arg.fh_out),
                offset: self.arg.off_out,
            }
        }
        /// Number of bytes to copy
        pub fn len(&self) -> u64 {
            self.arg.len
        }
        // API TODO: Return a specific flags type
        pub fn flags(&self) -> u64 {
            self.arg.flags
        }
    }

    /// `MacOS` only: Rename the volume. Set `fuse_init_out.flags` during init to
    /// `FUSE_VOL_RENAME` to enable
    #[cfg(target_os = "macos")]
    #[derive(Debug)]
    pub struct SetVolName<'a> {
        header: &'a fuse_in_header,
        name: &'a OsStr,
    }
    #[cfg(target_os = "macos")]
    impl_request!(SetVolName<'a>);
    #[cfg(target_os = "macos")]
    impl<'a> SetVolName<'a> {
        pub fn name(&self) -> &'a OsStr {
            self.name
        }
    }

    /// `MacOS` only: Query extended times (`bkuptime` and `crtime`). Set `fuse_init_out.flags`
    /// during init to `FUSE_XTIMES` to enable
    #[cfg(target_os = "macos")]
    #[derive(Debug)]
    pub struct GetXTimes<'a> {
        header: &'a fuse_in_header,
    }
    #[cfg(target_os = "macos")]
    impl_request!(GetXTimes<'a>);
    // API TODO: Consider `rename2(RENAME_EXCHANGE)`
    /// `MacOS` only (undocumented)
    #[cfg(target_os = "macos")]
    #[derive(Debug)]
    pub struct Exchange<'a> {
        header: &'a fuse_in_header,
        arg: &'a fuse_exchange_in,
        oldname: &'a Path,
        newname: &'a Path,
    }
    #[cfg(target_os = "macos")]
    impl_request!(Exchange<'a>);
    #[cfg(target_os = "macos")]
    impl<'a> Exchange<'a> {
        pub fn from(&self) -> FilenameInDir<'a> {
            FilenameInDir::<'a> {
                dir: INodeNo(self.arg.olddir),
                name: self.oldname,
            }
        }
        pub fn to(&self) -> FilenameInDir<'a> {
            FilenameInDir::<'a> {
                dir: INodeNo(self.arg.newdir),
                name: self.newname,
            }
        }
        pub fn options(&self) -> u64 {
            self.arg.options
        }
    }
    /// TODO: Document
    #[derive(Debug)]
    pub struct CuseInit<'a> {
        header: &'a fuse_in_header,
        #[allow(unused)]
        arg: &'a fuse_init_in,
    }
    impl_request!(CuseInit<'a>);

    fn system_time_from_time(secs: i64, nsecs: u32) -> SystemTime {
        if secs >= 0 {
            SystemTime::UNIX_EPOCH + Duration::new(secs as u64, nsecs)
        } else {
            SystemTime::UNIX_EPOCH - Duration::new((-secs) as u64, nsecs)
        }
    }
    pub(crate) fn parse<'a>(
        header: &'a fuse_in_header,
        opcode: &fuse_opcode,
        data: &'a [u8],
    ) -> Option<Operation<'a>> {
        let mut data = ArgumentIterator::new(data);
        Some(match opcode {
            fuse_opcode::FUSE_LOOKUP => Operation::Lookup(Lookup {
                header,
                name: data.fetch_str()?,
            }),
            fuse_opcode::FUSE_FORGET => Operation::Forget(Forget {
                header,
                arg: data.fetch()?,
            }),
            fuse_opcode::FUSE_GETATTR => Operation::GetAttr(GetAttr {
                header,

                arg: data.fetch()?,
            }),
            fuse_opcode::FUSE_SETATTR => Operation::SetAttr(SetAttr {
                header,
                arg: data.fetch()?,
            }),
            fuse_opcode::FUSE_READLINK => Operation::ReadLink(ReadLink { header }),
            fuse_opcode::FUSE_SYMLINK => Operation::SymLink(SymLink {
                header,
                link_name: data.fetch_str()?.as_ref(),
                target: data.fetch_str()?.as_ref(),
            }),
            fuse_opcode::FUSE_MKNOD => Operation::MkNod(MkNod {
                header,
                arg: data.fetch()?,
                name: data.fetch_str()?.as_ref(),
            }),
            fuse_opcode::FUSE_MKDIR => Operation::MkDir(MkDir {
                header,
                arg: data.fetch()?,
                name: data.fetch_str()?.as_ref(),
            }),
            fuse_opcode::FUSE_UNLINK => Operation::Unlink(Unlink {
                header,
                name: data.fetch_str()?.as_ref(),
            }),
            fuse_opcode::FUSE_RMDIR => Operation::RmDir(RmDir {
                header,
                name: data.fetch_str()?.as_ref(),
            }),
            fuse_opcode::FUSE_RENAME => Operation::Rename(Rename {
                header,
                arg: data.fetch()?,
                name: data.fetch_str()?.as_ref(),
                newname: data.fetch_str()?.as_ref(),
            }),
            fuse_opcode::FUSE_LINK => Operation::Link(Link {
                header,
                arg: data.fetch()?,
                name: data.fetch_str()?.as_ref(),
            }),
            fuse_opcode::FUSE_OPEN => Operation::Open(Open {
                header,
                arg: data.fetch()?,
            }),
            fuse_opcode::FUSE_READ => Operation::Read(Read {
                header,
                arg: data.fetch()?,
            }),
            fuse_opcode::FUSE_WRITE => Operation::Write({
                let out = Write {
                    header,
                    arg: data.fetch()?,
                    data: data.fetch_all(),
                };
                assert!(out.data().len() == out.arg.size as usize);
                out
            }),
            fuse_opcode::FUSE_STATFS => Operation::StatFs(StatFs { header }),
            fuse_opcode::FUSE_RELEASE => Operation::Release(Release {
                header,
                arg: data.fetch()?,
            }),
            fuse_opcode::FUSE_FSYNC => Operation::FSync(FSync {
                header,
                arg: data.fetch()?,
            }),
            fuse_opcode::FUSE_SETXATTR => Operation::SetXAttr({
                let out = SetXAttr {
                    header,
                    arg: data.fetch()?,
                    name: data.fetch_str()?,
                    value: data.fetch_all(),
                };
                assert!(out.value.len() == out.arg.size as usize);
                out
            }),
            fuse_opcode::FUSE_GETXATTR => Operation::GetXAttr(GetXAttr {
                header,
                arg: data.fetch()?,
                name: data.fetch_str()?,
            }),
            fuse_opcode::FUSE_LISTXATTR => Operation::ListXAttr(ListXAttr {
                header,
                arg: data.fetch()?,
            }),
            fuse_opcode::FUSE_REMOVEXATTR => Operation::RemoveXAttr(RemoveXAttr {
                header,
                name: data.fetch_str()?,
            }),
            fuse_opcode::FUSE_FLUSH => Operation::Flush(Flush {
                header,
                arg: data.fetch()?,
            }),
            fuse_opcode::FUSE_INIT => Operation::Init(Init {
                header,
                arg: data.fetch()?,
            }),
            fuse_opcode::FUSE_OPENDIR => Operation::OpenDir(OpenDir {
                header,
                arg: data.fetch()?,
            }),
            fuse_opcode::FUSE_READDIR => Operation::ReadDir(ReadDir {
                header,
                arg: data.fetch()?,
            }),
            fuse_opcode::FUSE_RELEASEDIR => Operation::ReleaseDir(ReleaseDir {
                header,
                arg: data.fetch()?,
            }),
            fuse_opcode::FUSE_FSYNCDIR => Operation::FSyncDir(FSyncDir {
                header,
                arg: data.fetch()?,
            }),
            fuse_opcode::FUSE_GETLK => Operation::GetLk(GetLk {
                header,
                arg: data.fetch()?,
            }),
            fuse_opcode::FUSE_SETLK => Operation::SetLk(SetLk {
                header,
                arg: data.fetch()?,
            }),
            fuse_opcode::FUSE_SETLKW => Operation::SetLkW(SetLkW {
                header,
                arg: data.fetch()?,
            }),
            fuse_opcode::FUSE_ACCESS => Operation::Access(Access {
                header,
                arg: data.fetch()?,
            }),
            fuse_opcode::FUSE_CREATE => Operation::Create(Create {
                header,
                arg: data.fetch()?,
                name: data.fetch_str()?.as_ref(),
            }),
            fuse_opcode::FUSE_INTERRUPT => Operation::Interrupt(Interrupt {
                header,
                arg: data.fetch()?,
            }),
            fuse_opcode::FUSE_BMAP => Operation::BMap(BMap {
                header,
                arg: data.fetch()?,
            }),
            fuse_opcode::FUSE_DESTROY => Operation::Destroy(Destroy { header }),
            fuse_opcode::FUSE_IOCTL => Operation::IoCtl(IoCtl {
                header,
                arg: data.fetch()?,
                data: data.fetch_all(),
            }),
            fuse_opcode::FUSE_POLL => Operation::Poll(Poll {
                header,
                arg: data.fetch()?,
            }),
            fuse_opcode::FUSE_NOTIFY_REPLY => Operation::NotifyReply(NotifyReply {
                header,
                arg: data.fetch_all(),
            }),
            fuse_opcode::FUSE_BATCH_FORGET => {
                let arg = data.fetch()?;
                Operation::BatchForget(BatchForget {
                    header,
                    arg,
                    nodes: data.fetch_slice(arg.count as usize)?,
                })
            }
            #[cfg(feature = "abi-7-19")]
            fuse_opcode::FUSE_FALLOCATE => Operation::FAllocate(FAllocate {
                header,
                arg: data.fetch()?,
            }),
            #[cfg(feature = "abi-7-21")]
            fuse_opcode::FUSE_READDIRPLUS => Operation::ReadDirPlus(ReadDirPlus {
                header,
                arg: data.fetch()?,
            }),
            #[cfg(feature = "abi-7-23")]
            fuse_opcode::FUSE_RENAME2 => Operation::Rename2(Rename2 {
                header,
                arg: data.fetch()?,
                name: data.fetch_str()?.as_ref(),
                newname: data.fetch_str()?.as_ref(),
                old_parent: INodeNo(header.nodeid),
            }),
            #[cfg(feature = "abi-7-24")]
            fuse_opcode::FUSE_LSEEK => Operation::Lseek(Lseek {
                header,
                arg: data.fetch()?,
            }),
            #[cfg(feature = "abi-7-28")]
            fuse_opcode::FUSE_COPY_FILE_RANGE => Operation::CopyFileRange(CopyFileRange {
                header,
                arg: data.fetch()?,
            }),

            #[cfg(target_os = "macos")]
            fuse_opcode::FUSE_SETVOLNAME => Operation::SetVolName(SetVolName {
                header,
                name: data.fetch_str()?,
            }),
            #[cfg(target_os = "macos")]
            fuse_opcode::FUSE_GETXTIMES => Operation::GetXTimes(GetXTimes { header }),
            #[cfg(target_os = "macos")]
            fuse_opcode::FUSE_EXCHANGE => Operation::Exchange(Exchange {
                header,
                arg: data.fetch()?,
                oldname: data.fetch_str()?.as_ref(),
                newname: data.fetch_str()?.as_ref(),
            }),

            fuse_opcode::CUSE_INIT => Operation::CuseInit(CuseInit {
                header,
                arg: data.fetch()?,
            }),
        })
    }
}
use op::*;

/// Filesystem operation (and arguments) the kernel driver wants us to perform. The fields of each
/// variant needs to match the actual arguments the kernel driver sends for the specific operation.
#[derive(Debug)]
#[allow(missing_docs)]
pub enum Operation<'a> {
    Lookup(Lookup<'a>),
    Forget(Forget<'a>),
    GetAttr(GetAttr<'a>),
    SetAttr(SetAttr<'a>),
    #[allow(dead_code)]
    ReadLink(ReadLink<'a>),
    SymLink(SymLink<'a>),
    MkNod(MkNod<'a>),
    MkDir(MkDir<'a>),
    Unlink(Unlink<'a>),
    RmDir(RmDir<'a>),
    Rename(Rename<'a>),
    Link(Link<'a>),
    Open(Open<'a>),
    Read(Read<'a>),
    Write(Write<'a>),
    #[allow(dead_code)]
    StatFs(StatFs<'a>),
    Release(Release<'a>),
    FSync(FSync<'a>),
    SetXAttr(SetXAttr<'a>),
    GetXAttr(GetXAttr<'a>),
    ListXAttr(ListXAttr<'a>),
    RemoveXAttr(RemoveXAttr<'a>),
    Flush(Flush<'a>),
    Init(Init<'a>),
    OpenDir(OpenDir<'a>),
    ReadDir(ReadDir<'a>),
    ReleaseDir(ReleaseDir<'a>),
    FSyncDir(FSyncDir<'a>),
    GetLk(GetLk<'a>),
    SetLk(SetLk<'a>),
    SetLkW(SetLkW<'a>),
    Access(Access<'a>),
    Create(Create<'a>),
    Interrupt(Interrupt<'a>),
    BMap(BMap<'a>),
    Destroy(Destroy<'a>),
    IoCtl(IoCtl<'a>),
    Poll(Poll<'a>),
    #[allow(dead_code)]
    NotifyReply(NotifyReply<'a>),
    BatchForget(BatchForget<'a>),
    #[cfg(feature = "abi-7-19")]
    FAllocate(FAllocate<'a>),
    #[cfg(feature = "abi-7-21")]
    ReadDirPlus(ReadDirPlus<'a>),
    #[cfg(feature = "abi-7-23")]
    Rename2(Rename2<'a>),
    #[cfg(feature = "abi-7-24")]
    Lseek(Lseek<'a>),
    #[cfg(feature = "abi-7-28")]
    CopyFileRange(CopyFileRange<'a>),

    #[cfg(target_os = "macos")]
    SetVolName(SetVolName<'a>),
    #[cfg(target_os = "macos")]
    GetXTimes(GetXTimes<'a>),
    #[cfg(target_os = "macos")]
    Exchange(Exchange<'a>),

    #[allow(dead_code)]
    CuseInit(CuseInit<'a>),
}

impl fmt::Display for Operation<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Operation::Lookup(x) => write!(f, "LOOKUP name {:?}", x.name()),
            Operation::Forget(x) => write!(f, "FORGET nlookup {}", x.nlookup()),
            Operation::GetAttr(_) => write!(f, "GETATTR"),
            Operation::SetAttr(x) => x.fmt(f),
            Operation::ReadLink(_) => write!(f, "READLINK"),
            Operation::SymLink(x) => {
                write!(
                    f,
                    "SYMLINK target {:?}, link_name {:?}",
                    x.target(),
                    x.link_name()
                )
            }
            Operation::MkNod(x) => write!(
                f,
                "MKNOD name {:?}, mode {:#05o}, rdev {}",
                x.name(),
                x.mode(),
                x.rdev()
            ),
            Operation::MkDir(x) => write!(f, "MKDIR name {:?}, mode {:#05o}", x.name(), x.mode()),
            Operation::Unlink(x) => write!(f, "UNLINK name {:?}", x.name()),
            Operation::RmDir(x) => write!(f, "RMDIR name {:?}", x.name),
            Operation::Rename(x) => write!(f, "RENAME src {:?}, dest {:?}", x.src(), x.dest()),
            Operation::Link(x) => write!(f, "LINK ino {:?}, dest {:?}", x.inode_no(), x.dest()),
            Operation::Open(x) => write!(f, "OPEN flags {:#x}", x.flags()),
            Operation::Read(x) => write!(
                f,
                "READ fh {:?}, offset {}, size {}",
                x.file_handle(),
                x.offset(),
                x.size()
            ),
            Operation::Write(x) => write!(
                f,
                "WRITE fh {:?}, offset {}, size {}, write flags {:#x}",
                x.file_handle(),
                x.offset(),
                x.data().len(),
                x.write_flags()
            ),
            Operation::StatFs(_) => write!(f, "STATFS"),
            Operation::Release(x) => write!(
                f,
                "RELEASE fh {:?}, flags {:#x}, flush {}, lock owner {:?}",
                x.file_handle(),
                x.flags(),
                x.flush(),
                x.lock_owner()
            ),
            Operation::FSync(x) => write!(
                f,
                "FSYNC fh {:?}, fsync fdatasync {}",
                x.file_handle(),
                x.fdatasync()
            ),
            Operation::SetXAttr(x) => write!(
                f,
                "SETXATTR name {:?}, size {}, flags {:#x}",
                x.name(),
                x.value().len(),
                x.flags()
            ),
            Operation::GetXAttr(x) => {
                write!(f, "GETXATTR name {:?}, size {:?}", x.name(), x.size())
            }
            Operation::ListXAttr(x) => write!(f, "LISTXATTR size {}", x.size()),
            Operation::RemoveXAttr(x) => write!(f, "REMOVEXATTR name {:?}", x.name()),
            Operation::Flush(x) => write!(
                f,
                "FLUSH fh {:?}, lock owner {:?}",
                x.file_handle(),
                x.lock_owner()
            ),
            Operation::Init(x) => write!(
                f,
                "INIT kernel ABI {}, capabilities {:#x}, max readahead {}",
                x.version(),
                x.capabilities(),
                x.max_readahead()
            ),
            Operation::OpenDir(x) => write!(f, "OPENDIR flags {:#x}", x.flags()),
            Operation::ReadDir(x) => write!(
                f,
                "READDIR fh {:?}, offset {}, size {}",
                x.file_handle(),
                x.offset(),
                x.size()
            ),
            Operation::ReleaseDir(x) => write!(
                f,
                "RELEASEDIR fh {:?}, flags {:#x}, flush {}, lock owner {:?}",
                x.file_handle(),
                x.flags(),
                x.flush(),
                x.lock_owner()
            ),
            Operation::FSyncDir(x) => write!(
                f,
                "FSYNCDIR fh {:?}, fsync fdatasync: {}",
                x.file_handle(),
                x.fdatasync()
            ),
            Operation::GetLk(x) => write!(
                f,
                "GETLK fh {:?}, lock owner {:?}",
                x.file_handle(),
                x.lock_owner()
            ),
            Operation::SetLk(x) => write!(
                f,
                "SETLK fh {:?}, lock owner {:?}",
                x.file_handle(),
                x.lock_owner()
            ),
            Operation::SetLkW(x) => write!(
                f,
                "SETLKW fh {:?}, lock owner {:?}",
                x.file_handle(),
                x.lock_owner()
            ),
            Operation::Access(x) => write!(f, "ACCESS mask {:#05o}", x.mask()),
            Operation::Create(x) => write!(
                f,
                "CREATE name {:?}, mode {:#05o}, flags {:#x}",
                x.name(),
                x.mode(),
                x.flags()
            ),
            Operation::Interrupt(x) => write!(f, "INTERRUPT unique {:?}", x.unique()),
            Operation::BMap(x) => write!(f, "BMAP blocksize {}, ids {}", x.block_size(), x.block()),
            Operation::Destroy(_) => write!(f, "DESTROY"),
            Operation::IoCtl(x) => write!(
                f,
                "IOCTL fh {:?}, cmd {}, data size {}, flags {:#x}",
                x.file_handle(),
                x.command(),
                x.in_data().len(),
                x.flags()
            ),
            Operation::Poll(x) => write!(f, "POLL fh {:?}", x.file_handle()),
            Operation::NotifyReply(_) => write!(f, "NOTIFYREPLY"),
            Operation::BatchForget(x) => write!(f, "BATCHFORGET nodes {:?}", x.nodes()),
            #[cfg(feature = "abi-7-19")]
            Operation::FAllocate(_) => write!(f, "FALLOCATE"),
            #[cfg(feature = "abi-7-21")]
            Operation::ReadDirPlus(x) => write!(
                f,
                "READDIRPLUS fh {:?}, offset {}, size {}",
                x.file_handle(),
                x.offset(),
                x.size()
            ),
            #[cfg(feature = "abi-7-23")]
            Operation::Rename2(x) => write!(f, "RENAME2 from {:?}, to {:?}", x.from(), x.to()),
            #[cfg(feature = "abi-7-24")]
            Operation::Lseek(x) => write!(
                f,
                "LSEEK fh {:?}, offset {}, whence {}",
                x.file_handle(),
                x.offset(),
                x.whence()
            ),
            #[cfg(feature = "abi-7-28")]
            Operation::CopyFileRange(x) => write!(
                f,
                "COPY_FILE_RANGE src {:?}, dest {:?}, len {}",
                x.src(),
                x.dest(),
                x.len()
            ),

            #[cfg(target_os = "macos")]
            Operation::SetVolName(x) => write!(f, "SETVOLNAME name {:?}", x.name()),
            #[cfg(target_os = "macos")]
            Operation::GetXTimes(_) => write!(f, "GETXTIMES"),
            #[cfg(target_os = "macos")]
            Operation::Exchange(x) => write!(
                f,
                "EXCHANGE from {:?}, to {:?}, options {:#x}",
                x.from(),
                x.to(),
                x.options()
            ),

            Operation::CuseInit(_) => write!(f, "CUSE_INIT"),
        }
    }
}

/// Low-level request of a filesystem operation the kernel driver wants to perform.
#[derive(Debug)]
pub struct AnyRequest<'a> {
    header: &'a fuse_in_header,
    data: &'a [u8],
}
impl_request!(AnyRequest<'_>);

impl<'a> AnyRequest<'a> {
    pub fn operation(&self) -> Result<Operation<'a>, RequestError> {
        // Parse/check opcode
        let opcode = fuse_opcode::try_from(self.header.opcode)
            .map_err(|_: InvalidOpcodeError| RequestError::UnknownOperation(self.header.opcode))?;
        // Parse/check operation arguments
        op::parse(self.header, &opcode, self.data).ok_or(RequestError::InsufficientData)
    }
}

impl fmt::Display for AnyRequest<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if let Ok(op) = self.operation() {
            write!(
                f,
                "FUSE({:3}) ino {:#018x} {}",
                self.header.unique, self.header.nodeid, op
            )
        } else {
            write!(
                f,
                "FUSE({:3}) ino {:#018x}",
                self.header.unique, self.header.nodeid
            )
        }
    }
}

impl<'a> TryFrom<&'a [u8]> for AnyRequest<'a> {
    type Error = RequestError;

    fn try_from(data: &'a [u8]) -> Result<Self, Self::Error> {
        // Parse a raw packet as sent by the kernel driver into typed data. Every request always
        // begins with a `fuse_in_header` struct followed by arguments depending on the opcode.
        let data_len = data.len();
        let mut arg_iter = ArgumentIterator::new(data);
        // Parse header
        let header: &fuse_in_header = arg_iter
            .fetch()
            .ok_or_else(|| RequestError::ShortReadHeader(arg_iter.len()))?;
        // Check data size
        if data_len < header.len as usize {
            return Err(RequestError::ShortRead(data_len, header.len as usize));
        }
        Ok(Self {
            header,
            data: &data[mem::size_of::<fuse_in_header>()..header.len as usize],
        })
    }
}

#[cfg(test)]
mod tests {
    use super::super::test::AlignedData;
    use super::*;
    use std::ffi::OsStr;

    #[cfg(all(target_endian = "big", not(feature = "abi-7-36")))]
    const INIT_REQUEST: AlignedData<[u8; 56]> = AlignedData([
        // decimal 56 == hex 0x38
        0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x1a, // len, opcode
        0xde, 0xad, 0xbe, 0xef, 0xba, 0xad, 0xd0, 0x0d, // unique
        0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, // nodeid
        0xc0, 0x01, 0xd0, 0x0d, 0xc0, 0x01, 0xca, 0xfe, // uid, gid
        0xc0, 0xde, 0xba, 0x5e, 0x00, 0x00, 0x00, 0x00, // pid, padding
        0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, // major, minor
        0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, // max_readahead, flags
    ]);

    #[cfg(all(target_endian = "little", not(feature = "abi-7-36")))]
    const INIT_REQUEST: AlignedData<[u8; 56]> = AlignedData([
        // decimal 56 == hex 0x38
        0x38, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, // len, opcode
        0x0d, 0xf0, 0xad, 0xba, 0xef, 0xbe, 0xad, 0xde, // unique
        0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, // nodeid
        0x0d, 0xd0, 0x01, 0xc0, 0xfe, 0xca, 0x01, 0xc0, // uid, gid
        0x5e, 0xba, 0xde, 0xc0, 0x00, 0x00, 0x00, 0x00, // pid, padding
        0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, // major, minor
        0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // max_readahead, flags
    ]);

    #[cfg(all(target_endian = "big", feature = "abi-7-36"))]
    const INIT_REQUEST: AlignedData<[u8; 104]> = AlignedData([
        // decimal 104 == hex 0x68
        0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x1a, // len, opcode
        0xde, 0xad, 0xbe, 0xef, 0xba, 0xad, 0xd0, 0x0d, // unique
        0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, // nodeid
        0xc0, 0x01, 0xd0, 0x0d, 0xc0, 0x01, 0xca, 0xfe, // uid, gid
        0xc0, 0xde, 0xba, 0x5e, 0x00, 0x00, 0x00, 0x00, // pid, padding
        0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, // major, minor
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // max_readahead, flags
        0x00, 0x00, 0x00, 0x00, // flags2 //TODO: nonzero data
        0x00, 0x00, 0x00, 0x00, // eleven unused fields
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    ]);

    #[cfg(all(target_endian = "little", feature = "abi-7-36"))]
    const INIT_REQUEST: AlignedData<[u8; 104]> = AlignedData([
        // decimal 104 == hex 0x68
        0x68, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, // len, opcode
        0x0d, 0xf0, 0xad, 0xba, 0xef, 0xbe, 0xad, 0xde, // unique
        0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, // nodeid
        0x0d, 0xd0, 0x01, 0xc0, 0xfe, 0xca, 0x01, 0xc0, // uid, gid
        0x5e, 0xba, 0xde, 0xc0, 0x00, 0x00, 0x00, 0x00, // pid, padding
        0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, // major, minor
        0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // max_readahead, flags
        0x00, 0x00, 0x00, 0x00, // flags2 //TODO: nonzero data
        0x00, 0x00, 0x00, 0x00, // eleven unused fields
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    ]);

    #[cfg(target_endian = "big")]
    const MKNOD_REQUEST: AlignedData<[u8; 56]> = [
        0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x08, // len, opcode
        0xde, 0xad, 0xbe, 0xef, 0xba, 0xad, 0xd0, 0x0d, // unique
        0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, // nodeid
        0xc0, 0x01, 0xd0, 0x0d, 0xc0, 0x01, 0xca, 0xfe, // uid, gid
        0xc0, 0xde, 0xba, 0x5e, 0x00, 0x00, 0x00, 0x00, // pid, padding
        0x00, 0x00, 0x01, 0xa4, 0x00, 0x00, 0x00, 0x00, // mode, rdev
        0x66, 0x6f, 0x6f, 0x2e, 0x74, 0x78, 0x74, 0x00, // name
    ];

    #[cfg(target_endian = "little")]
    const MKNOD_REQUEST: AlignedData<[u8; 64]> = AlignedData([
        0x40, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, // len, opcode
        0x0d, 0xf0, 0xad, 0xba, 0xef, 0xbe, 0xad, 0xde, // unique
        0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, // nodeid
        0x0d, 0xd0, 0x01, 0xc0, 0xfe, 0xca, 0x01, 0xc0, // uid, gid
        0x5e, 0xba, 0xde, 0xc0, 0x00, 0x00, 0x00, 0x00, // pid, padding
        0xa4, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mode, rdev
        0xed, 0x01, 0x00, 0x00, 0xe7, 0x03, 0x00, 0x00, // umask, padding
        0x66, 0x6f, 0x6f, 0x2e, 0x74, 0x78, 0x74, 0x00, // name
    ]);

    #[test]
    fn short_read_header() {
        match AnyRequest::try_from(&INIT_REQUEST[..20]) {
            Err(RequestError::ShortReadHeader(20)) => (),
            _ => panic!("Unexpected request parsing result"),
        }
    }

    #[test]
    fn short_read() {
        match AnyRequest::try_from(&INIT_REQUEST[..48]) {
            #[cfg(not(feature = "abi-7-36"))]
            Err(RequestError::ShortRead(48, 56)) => (),
            #[cfg(feature = "abi-7-36")]
            Err(RequestError::ShortRead(48, 104)) => (),
            _ => panic!("Unexpected request parsing result"),
        }
    }

    #[test]
    fn init() {
        let req = AnyRequest::try_from(&INIT_REQUEST[..]).unwrap();
        #[cfg(not(feature = "abi-7-36"))]
        assert_eq!(req.header.len, 56);
        #[cfg(feature = "abi-7-36")]
        assert_eq!(req.header.len, 104);
        assert_eq!(req.header.opcode, 26);
        assert_eq!(req.unique(), RequestId(0xdead_beef_baad_f00d));
        assert_eq!(req.nodeid(), INodeNo(0x1122_3344_5566_7788));
        assert_eq!(req.uid(), 0xc001_d00d);
        assert_eq!(req.gid(), 0xc001_cafe);
        assert_eq!(req.pid(), 0xc0de_ba5e);
        match req.operation().unwrap() {
            Operation::Init(x) => {
                assert_eq!(x.version(), Version(7, 8));
                assert_eq!(x.max_readahead(), 4096);
            }
            _ => panic!("Unexpected request operation"),
        }
    }

    #[test]
    fn mknod() {
        let req = AnyRequest::try_from(&MKNOD_REQUEST[..]).unwrap();
        assert_eq!(req.header.len, 64);
        assert_eq!(req.header.opcode, 8);
        assert_eq!(req.unique(), RequestId(0xdead_beef_baad_f00d));
        assert_eq!(req.nodeid(), INodeNo(0x1122_3344_5566_7788));
        assert_eq!(req.uid(), 0xc001_d00d);
        assert_eq!(req.gid(), 0xc001_cafe);
        assert_eq!(req.pid(), 0xc0de_ba5e);
        match req.operation().unwrap() {
            Operation::MkNod(x) => {
                assert_eq!(x.mode(), 0o644);
                assert_eq!(x.umask(), 0o755);
                assert_eq!(x.name(), OsStr::new("foo.txt"));
            }
            _ => panic!("Unexpected request operation"),
        }
    }
}
